﻿using AcAp = Autodesk.AutoCAD.ApplicationServices;
using AcRx = Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.AutoCAD.Colors;

namespace DonetArxLib.CuveProjection
{
    internal class CurveProcess
    {
        static string ERR_LAYER_NAME = "ErrLayer";
        static string RESULT_LAYER_NAME = "ResultLayer";
        static string TEMP_LAYER_NAME = "TempLayer";

        static double TOL = 1e-3;

        static List<string> _colors = new List<string>
        {
            ERR_LAYER_NAME,
            RESULT_LAYER_NAME,
            TEMP_LAYER_NAME
        };

        List<Polyline2d> _resultLines = new List<Polyline2d>();
        List<Polyline2d> _plsTotal = new List<Polyline2d>();
        List<List<Polyline2d>> _joinLines = new List<List<Polyline2d>>();

        public CurveProcess()
        {
            var doc = AcAp.Application.DocumentManager.MdiActiveDocument;
            if (doc == null)
                throw new InvalidOperationException("no document!");

            UtilFunc.EnsureLayerExist(doc.Database, ERR_LAYER_NAME, (short)_colors.IndexOf(ERR_LAYER_NAME));
            UtilFunc.EnsureLayerExist(doc.Database, RESULT_LAYER_NAME, (short)_colors.IndexOf(RESULT_LAYER_NAME));
            UtilFunc.EnsureLayerExist(doc.Database, TEMP_LAYER_NAME, (short)_colors.IndexOf(TEMP_LAYER_NAME));
        }

        public List<ObjectId> SelectPlines()
        {
            var doc = AcAp.Application.DocumentManager.MdiActiveDocument;
            if (doc == null)
                throw new ArgumentNullException("no document!");

            var result = new List<ObjectId>();

            var ed = doc.Editor;
            var opt = new PromptSelectionOptions();
            opt.MessageForAdding = "请选择要处理的线";

            TypedValue[] tv = new TypedValue[1];
            tv.SetValue(new TypedValue((int)DxfCode.Start, "POLYLINE"), 0);
            SelectionFilter sf = new SelectionFilter(tv);
            var ssresult = ed.GetSelection(opt, sf);
            if (ssresult.Status != PromptStatus.OK)
                return result;

            var ss = ssresult.Value;
            if (ss.Count < 1)
                return result;

            for (int i = 0; i < ss.Count; i++)
                result.Add(ss[i].ObjectId);

            return result;
        }

        public List<ObjectId> GetResultLines()
        {
            return _resultLines.Select(l => l.ObjectId).ToList();
        }

        public void PickPropLines(List<ObjectId> ids)
        {
            if (ids == null)
                throw new ArgumentNullException(nameof(ids));

            var trans = UtilFunc.GetTopTransation();

            foreach (var id in ids)
            {
                var obj = trans.GetObject(id, OpenMode.ForRead);
                if (obj == null)
                    continue;

                if (obj is Polyline2d pl)
                    _plsTotal.Add(pl);
                else if (obj is Entity ent)
                    SetLayer(ent, ERR_LAYER_NAME);
                else
                    throw new InvalidOperationException("incorrect entity type!");
            }
        }

        public void JoinCurve()
        {
            if (_plsTotal.Count < 1)
                throw new InvalidOperationException("no lines to join!");


            while (_plsTotal.Count > 0)
            {
                var curCurve = _plsTotal.First();
                _plsTotal.RemoveAt(0);

                List<Polyline2d> sameLine = new List<Polyline2d>();
                sameLine.Add(curCurve);

                while (TryFindJoinedCurve(sameLine)) { }

                _joinLines.Add(sameLine);
            }

            foreach (var joinLine in _joinLines)
            {
                var joinedLine = JoinSinglePolyline(joinLine);
                if (joinedLine != null)
                    _resultLines.Add(joinedLine); 
            }

            // set direction
            foreach (Polyline2d pl in _resultLines)
            {
                if (pl.StartPoint.Y < pl.EndPoint.Y)
                {
                    pl.UpgradeOpen();
                    pl.ReverseCurve();
                    pl.DowngradeOpen();
                }    
            }

            // sort by startPoint y position
            //_resultLines.OrderBy(p => p.StartPoint.Y);
        }

        public void MoveResult()
        {
            int countInRow = 10;
            double width = 4000;
            double height = 7000;

            int label = 1;
            int row = 0;
            int col = 0;
            foreach (var polyline in _resultLines)
            {
                if (col >= countInRow)
                {
                    col = 0;
                    row++;
                }

                double xMove = width * col;
                double yMove = height * row * -1;

                Matrix3d mtx = Matrix3d.Displacement(new Vector3d(xMove, yMove,0));

                polyline.UpgradeOpen();
                polyline.TransformBy(mtx); 
                polyline.DowngradeOpen();

                AddText(label.ToString(), TEMP_LAYER_NAME, polyline.StartPoint, polyline.Database);

                col++;
                label++;
            }
        }

        public void FlatCurve()
        {
            foreach (var polyline in _resultLines)
            {
                polyline.UpgradeOpen();
                polyline.Normal = Vector3d.ZAxis;
                polyline.Elevation = 0;
                polyline.DowngradeOpen();
            }
        }

        public void ShowJoindResult()
        {
            foreach (var polyline in _joinLines)
                foreach (var line in polyline)
                    SetLayer(line, TEMP_LAYER_NAME);
        }

        private bool TryFindJoinedCurve(List<Polyline2d> sameLines)
        {
            bool isFind = false;
            if (_plsTotal.Count < 1)
                return false;

            // head join
            var startPt = sameLines.First().StartPoint;
            var pl = _plsTotal.FirstOrDefault(p => p.EndPoint.AreValueEqual(startPt));
            if (pl != null)
            {
                _plsTotal.Remove(pl);
                sameLines.Insert(0, pl);
                isFind = true;

                if (_plsTotal.Count < 1)
                    return false;
            }

            // tail join
            var endPt = sameLines.Last().EndPoint;
            pl = _plsTotal.FirstOrDefault(p => p.StartPoint.AreValueEqual(endPt));
            if (pl != null)
            {
                _plsTotal.Remove(pl);
                sameLines.Add(pl);
                isFind=true;
            }

            return isFind;
        }

        public static void SetLayer(Entity ent, string layerName)
        {
            UtilFunc.EnsureLayerExist(ent.Database, layerName, (short)_colors.IndexOf(layerName));
            ent.UpgradeOpen();
            ent.ColorIndex = 256;
            ent.Layer = layerName;
            ent.DowngradeOpen();
        }

        public Polyline2d JoinSinglePolyline(List<Polyline2d> pls)
        {
            var result = new Polyline2d();
            result.PolyType = Poly2dType.SimplePoly;
            
            var maxVtxPl = GetMaxVertexPl(pls, out int maxCount);
            if (maxCount <= 2)
                throw new InvalidOperationException("max vertex count = 2!, can't join!");

            var maxEvel = maxVtxPl.Elevation;
            var index = pls.IndexOf(maxVtxPl);

            var clone = maxVtxPl.Clone() as Polyline2d;
            clone.ColorIndex = 256;
            UtilFunc.AddToCurrentSpace(clone, UtilFunc.GetTopTransation(), RESULT_LAYER_NAME);

            // headJoin
            for (int i=index -1; i>=0; i--)
            {
                var prePl = pls[i];
                HeadJoin(clone, prePl);
            }

            // tailJoin
            for (int i=index+1; i<pls.Count; i++)
            {
                var nextPl = pls[i];
                TailJoin(clone, nextPl);
            }

            // reset start point from foot.
            if (clone.StartPoint.Z > clone.EndPoint.Z)
                clone.ReverseCurve();

            return clone;
        }

        public void AddLabel()
        {
            int index = 1;
            foreach (var pl in _resultLines)
            {
                AddText(index.ToString(), TEMP_LAYER_NAME, pl.StartPoint, pl.Database);
                index++;
            }
        }

        private void AddText(string content, string layer, Point3d position, Database db)
        {
            var text = new DBText();
            text.SetDatabaseDefaults();
            text.TextString = content;
            text.Height = 50;
            text.Position = position;
            text.HorizontalMode = TextHorizontalMode.TextLeft;
            text.VerticalMode = TextVerticalMode.TextBottom;
            UtilFunc.AddToCurrentSpace(text, UtilFunc.GetTopTransation(), layer);
            text.AlignmentPoint = position;
            text.AdjustAlignment(db);
        }

        private void HeadJoin(Polyline2d curPl, Polyline2d prePl)
        {
            var vtxes = GetVertexes(prePl);
            if (vtxes.Count < 2)
                throw new InvalidOperationException("poly vertex count < 2");

            var trans = UtilFunc.GetTopTransation();

            var mtx = curPl.Ecs.Inverse();

            if (vtxes.Count == 2)
            {
                var vtxPos = prePl.StartPoint.TransformBy(mtx);
                var vtx = new Vertex2d(new Point3d(vtxPos.X, vtxPos.Y, 0), 0, 0, 0, 0);

                curPl.UpgradeOpen();
                curPl.InsertVertexAt(null, vtx);
                trans.AddNewlyCreatedDBObject(vtx, true);
                curPl.DowngradeOpen();

            }
            else if (prePl.Normal.AreValueEqual(curPl.Normal) && Math.Abs(prePl.Elevation - curPl.Elevation) < TOL)
            {
                curPl.UpgradeOpen();
                for (int i=vtxes.Count-2; i>=0; i--)
                {
                    var vtxPos = prePl.VertexPosition(vtxes[i]).TransformBy(mtx);
                    var vtx = new Vertex2d(new Point3d(vtxPos.X, vtxPos.Y, 0), 0, 0, 0, 0);

                    curPl.InsertVertexAt(null, vtx);
                    trans.AddNewlyCreatedDBObject(vtx, true);
                }
                curPl.DowngradeOpen();
            }
            else
                throw new InvalidOperationException("poly normal or elevation not equal!");
        }

        private void TailJoin(Polyline2d curPl, Polyline2d nextPl)
        {
            var vtxes = GetVertexes(nextPl);
            if (vtxes.Count < 2)
                throw new InvalidOperationException("poly vertex count < 2");

            var trans = UtilFunc.GetTopTransation();

            var mtx = curPl.Ecs.Inverse();
            
            if (vtxes.Count == 2)
            {
                var vtxPos = nextPl.EndPoint.TransformBy(mtx);
                var vtx = new Vertex2d(new Point3d(vtxPos.X, vtxPos.Y, 0), 0, 0, 0, 0);

                curPl.UpgradeOpen();
                curPl.AppendVertex(vtx);
                trans.AddNewlyCreatedDBObject(vtx, true);
                curPl.DowngradeOpen();
            }
            else if (nextPl.Normal.AreValueEqual(curPl.Normal) && Math.Abs(nextPl.Elevation - curPl.Elevation) < TOL)
            {
                curPl.UpgradeOpen();
                for (int i = 1; i < vtxes.Count; i++)
                {
                    var vtxPos = nextPl.VertexPosition(vtxes[i]).TransformBy(mtx);
                    var vtx = new Vertex2d(new Point3d(vtxPos.X, vtxPos.Y, 0), 0, 0, 0, 0);
                    curPl.AppendVertex(vtx);
                    trans.AddNewlyCreatedDBObject(vtx, true);
                }
                curPl.DowngradeOpen();
            }
            else 
                throw new InvalidOperationException("poly normal or elevation not equal!");
        }

        public static Polyline2d GetMaxVertexPl(List<Polyline2d> pls, out int maxCount)
        {
            maxCount = -1;
            var index = 0;
            for (int i = 0; i <pls.Count; i++)
            {
                var count = GetVertexCount(pls[i]);
                if (count > maxCount)
                {
                    maxCount = count;
                    index = i;
                }
            }

            return pls[index];
        }

        public static int GetVertexCount(Polyline2d pl)
        {
            int count = 0;
            foreach (var vtxId in pl)
                count++;

            return count;
        }

        public static List<Vertex2d> GetVertexes(Polyline2d pl)
        {
            var trans = UtilFunc.GetTopTransation();

            var result = new List<Vertex2d>();
            foreach (ObjectId vtxId in pl)
            {
                var vtx = trans.GetObject(vtxId, OpenMode.ForRead) as Vertex2d;
                if (vtx != null)
                    result.Add(vtx);
            }

            return result;
        }
    }
}
